feat(cursor): wire /models /agents /cancel to cursor-agent configOptions#492
feat(cursor): wire /models /agents /cancel to cursor-agent configOptions#492brettchien wants to merge 3 commits intoopenabdev:mainfrom
Conversation
OpenAB PR ScreeningThis is a manually uploaded screening report from the OpenAB project-screening flow for context collection and reviewer handoff.
Screening reportIntentpr the concrete problem is twofold. first, cursor-backed channels currently miss the intended assumption: deploy-time cursor support already exists in-tree through the existing cursor image, helm values, and docs, so this pr is intentionally about discord interaction flow and policy boundary, not first-time packaging. Featthis is a feature and interaction-hardening pass centered in in plain terms, it makes cursor sessions behave like a real first-class ACP backend for discord controls:
notably, the ACP transport, pool internals, and config parsing layer stay untouched. that is a good boundary. Who It Servesthe primary beneficiary is discord users running openab against secondarily it serves maintainers and deployers. the docs now make the startup model invariant and upstream cursor-agent defects explicit, which reduces false debugging loops where openab gets blamed for cursor's bad protocol behavior. Rewritten Promptimplement cursor-compatible discord config controls without adding cursor-specific heuristics to the ACP layer. acceptance criteria:
validation:
if issue Merge Pitchthis is worth merging because it closes a real product gap for cursor-backed deployments with relatively tight scope. users get the same slash-command control surface other ACP backends already expose, and the implementation does it in the right layer: discord handler and docs, not protocol-shaping hacks. risk is moderate but localized. the code changes sit in interaction timing and pooled session bootstrapping, which can regress command UX if wrong, but they do not alter the ACP core. the likely reviewer concern is not the handler logic itself. it is whether this pr is claiming to close more than it actually does, because Best-Practice ComparisonOpenClaw: the relevant principle is boundary discipline. route work explicitly, surface failures honestly, and do not smuggle backend-specific recovery rules into the wrong layer. this pr matches that better than the earlier probe-and-fallback direction. durable job persistence and retry/backoff are not directly relevant here because this is an interactive discord control path, not a scheduled execution pipeline. Hermes Agent: the relevant principle is making state transitions explicit before acting. the defer-first plus net: the useful lesson from both reference systems is not their runtime architecture. it is their refusal to paper over undefined state with wishful logic. Implementation Options
Comparison Table
Recommendationtake option 1. the pr is strongest when treated as a focused interaction-layer improvement plus documentation cleanup. it fixes a real user-facing gap, keeps the ACP boundary honest, and avoids merging policy-confused workaround code just because cursor-agent is sloppy. if maintainers still want a dedicated the one thing to watch in review is scope accounting: if |
Closes openabdev#489. Key changes: - discord slash handlers defer_ephemeral first, then get_or_create the pooled ACP session, then edit_response with the select menu built from the session's cached configOptions. - /models runs a silent "ping" probe after set_config_option to catch cursor-agent's soft-reject (`AI Model Not Found` / `Model name is not valid`) and falls back to `default[]` (Auto). Probe timeout is configurable via `[agent].probe_timeout_secs` (default 15s); on timeout the probe is cancelled and the same fallback runs. - Cap StringSelectMenu at Discord's 25-option limit, preserving the current value and `default[]` when truncating (cursor-agent returns 26 models on a Pro account). - Treat `agent` and `mode` categories as aliases so the same `/agents` command works against both kiro-cli (category=agent) and cursor-agent (category=mode). Adds `ProbeResult` + `probe_prompt` on AcpConnection and `probe_session` + `probe_timeout_secs` on SessionPool. Docs updated in `docs/cursor.md` with the /models behaviour + probe semantics. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Strip the proactive probe + auto-fallback-to-Auto workaround for cursor-agent's soft-reject behaviour. Per project policy we trust the ACP protocol: `setOk:true` means the switch succeeded. The upstream cursor-agent bug where `set_config_option` accepts unroutable models is tracked in openabdev#493 and reported via the Cursor forum. Retained: - /models, /agents, /cancel wired to ACP `configOptions` - `defer_ephemeral` + `edit_response` pattern to avoid Discord 3s deadline - Pre-flight `get_or_create` so the command works before first @mention - 25-option cap on Discord select menus (cursor-agent returns 26 models on Pro accounts); current/default pinned when truncating - `agent`/`mode` category alias for kiro-cli compatibility Removed: - `AcpConnection::probe_prompt` and `ProbeResult` (connection.rs) - `SessionPool::probe_session` and `probe_timeout_secs` (pool.rs) - `AgentConfig.probe_timeout_secs` field (config.rs) - `Handler::fallback_to_auto` and all /models probe logic (discord.rs) - `probe_timeout_secs` example in config.toml.example - Proactive-probe section in docs/cursor.md, replaced with "Soft rejects from unroutable models" passthrough explanation docs/cursor.md now includes a Known Limitations subsection listing the three upstream ACP compliance gaps (setOk for unroutable models, unfiltered model list, process-local loadSession), all linked to openabdev#493. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop the current-value + `default[]` pinning logic inside `build_config_select`. The pin was motivated by preserving Auto when truncating from 26 to 25 cursor-agent models, but `"default[]"` is a cursor-specific literal and the extra complexity buys little — Auto is already first in cursor-agent's model list, so a straight in-order `take(25)` keeps it. When `current_value` sits at position >25 the dropdown won't show it as the default, but the live session retains the selection on cursor's side; user can switch again by rerunning `/models`. This keeps `build_config_select` ACP-agnostic: no vendor literals, no assumptions about which option is the "fallback". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
87a0e09 to
85a22b4
Compare
masami-agent
left a comment
There was a problem hiding this comment.
PR Review: #492
Summary
- Problem: Discord slash commands
/models,/agents,/canceldid not work against cursor-agent because the handlers assumed a session already existed and usedcreate_responsewhich could exceed Discord's 3-second interaction deadline on cold starts. - Approach: Switch all three handlers to
defer_ephemeral+edit_response, add a pre-flightget_or_createto ensure a session (and its cachedconfigOptions) exists, aliasagent/modecategories for kiro-cli/cursor-agent compatibility, and cap the select menu at Discord's 25-option limit with a plaintake(25). - Risk level: Low
Core Assessment
- Problem clearly stated: ✅ — linked to #489, thorough PR description with revision history
- Approach appropriate: ✅ — minimal changes scoped to discord.rs, no ACP layer modifications
- Alternatives considered: ✅ — v1 (probe+fallback) → v2 (trust-the-protocol) → v3 (simplified truncation), well-documented iteration
- Best approach for now: ✅ — trust-the-protocol is the right call; upstream bugs belong upstream (#493)
Findings
build_config_select — category aliasing and truncation
The alias table is clean and handles the kiro-cli (agent) vs cursor-agent (mode) divergence correctly. The take(25) truncation is ACP-agnostic — no vendor literals, no assumptions about which option is the fallback. Good simplification from v3.
handle_config_command — defer + get_or_create + edit
The defer_ephemeral → get_or_create → get_config_options → edit_response sequence is correct. get_or_create handles both missing-session and evicted-session cases, and session/new populates config_options (confirmed in connection.rs:358), so the subsequent get_config_options call will have data. The error message on get_or_create failure is clear and actionable.
handle_cancel_command — defer + cancel
Using defer_ephemeral here is consistent with the other handlers. cancel_session uses pre-stored cancel_handles and doesn't require a live session lock, so the "no session for thread" error on missing session is fine UX — it tells the user there's nothing to cancel.
handle_config_select — defensive get_or_create
The defensive get_or_create before set_config_option is a good catch — the pool could evict the session between the initial /models interaction and the user's selection. This closes a real race window.
Docs (cursor.md)
The --model auto startup table is a valuable addition — this is a non-obvious gotcha. The "Soft rejects from unroutable models" section clearly explains the trust-the-protocol semantics and links to #493. Known Limitations is well-structured.
Review Summary
🔴 Blockers
(none)
💬 Questions
(none)
🔧 Suggested Changes
(none)
ℹ️ Info
- In
handle_config_select, thedisplay_nameextraction changed from.name.as_str()(borrowing) to.name.clone()(owning). Functionally identical since the value is immediately consumed byformat!(), but worth noting as a minor style divergence from the original. Not worth changing. - The commit history (v1 → v2 → v3) is well-structured and tells a clear story. The squash merge will collapse it, but the PR description preserves the rationale nicely.
⚪ Nits
(none)
Verdict
APPROVE — clean, well-scoped PR with no blockers. The defer+edit pattern, pre-flight session creation, and trust-the-protocol approach are all correct. Docs are thorough.
obrutjack
left a comment
There was a problem hiding this comment.
LGTM. Clean defer-first pattern, correct category aliasing, ACP layer untouched. Two minor suggestions (success-path error logging, alias comment) — non-blocking. Ship it.
shaun-agent
left a comment
There was a problem hiding this comment.
no blockers from my pass. the final revision keeps the fix in the discord handler layer: defer/edit interactions, get_or_create before config reads, agent/mode aliasing, and an ACP-agnostic 25-option cap.\n\nCI is green. local cargo test was not runnable in this pod because there is no C linker (cc), so build validation is from CI.
chaodu-agent
left a comment
There was a problem hiding this comment.
🔴 Needs Rebase + Rework — Good improvements but stale against main; regresses pagination.
Baseline Check (Step 0)
| Field | Value |
|---|---|
| State | OPEN |
| Mergeable | CONFLICTING |
| Created | 2026-04-20 (11 days ago) |
| Changes | +152 / -58 across 3 files |
| Closes | #489 |
Main branch status: Main now has pagination support (PR #687) for /models and /agents select menus. The PR's take(25) approach unknowingly regresses this.
Four-Question Framework
1. What problem does it solve?
/models, /agents, /cancel slash commands didn't work reliably against cursor-agent ACP sessions. Three issues: (a) "start a conversation first" dead-end, (b) Discord 3s interaction deadline exceeded on cold-start, (c) cursor-agent uses category=mode while kiro-cli uses category=agent.
2. How does it solve it?
- defer_ephemeral → edit_response pattern: Defers immediately (buys 15 min), then does slow work, then edits. Solves 3s deadline.
pool.get_or_create()pre-flight: Ensures active ACP session before reading configOptions.- agent/mode alias table: Treats
"agent"and"mode"as equivalent categories. take(25)truncation: Caps select menu at Discord's 25-option limit.- docs/cursor.md overhaul: Documents
--model autostartup semantics and known limitations.
3. What was considered?
Three iterations documented: v1 had proactive probe (removed), v2 trust-the-protocol, v3 removed vendor-specific pinning. Good iterative refinement.
4. Is it the best approach?
The defer + get_or_create + alias approach is sound. However, the take(25) flat truncation regresses main's pagination support.
Traffic Light
🟢 INFO
- defer_ephemeral pattern — Correct fix for Discord's 3s interaction deadline. Not on main yet; genuine improvement.
- get_or_create pre-flight — Eliminates UX dead-end. Well-placed defensive code.
- agent/mode alias — Clean solution for cross-backend category mismatch.
- Trust-the-protocol docs — cursor.md additions are excellent.
- Thorough test plan — Manual E2E against real cursor-agent pod.
🟡 NIT
- Use existing
SELECT_MENU_PAGE_SIZEconstant after rebase (don't introduce duplicateDISCORD_SELECT_MAX). aliasesslice allocation on every call — consider simple||condition instead.
🔴 SUGGESTED CHANGES
- Merge conflicts — rebase required. Main's #687 rewrote imports and added pagination infrastructure.
- Pagination regression — Main's
build_config_selecttakes apageparameter and supports pagination. The PR replaces this with flattake(25)that silently drops options. After rebase, integrate defer + get_or_create + alias into existing pagination-aware code. - handle_config_select defer/edit_response flow — Verify that
comp.defer()+comp.edit_response()produces the intended UX for component interactions.
Reviewed by 超渡法師 🔃 chaodu Backlog triage
chaodu-agent
left a comment
There was a problem hiding this comment.
🔴 Needs Rebase + Rework — Valuable improvements, but pagination regression must be fixed
📋 Baseline Check (Step 0)
Checked main branch first. Main now has:
SELECT_MENU_PAGE_SIZE = 25constantbuild_config_selecttakes apageparameter with full paginationhandle_paginationfor ◀/▶ button navigationbuild_pagination_buttonshelper
Main does NOT have: defer_ephemeral pattern, get_or_create pre-flight, or agent/mode alias. These are net-new value from this PR.
Critical finding: The PR's take(25) flat truncation regresses main's existing pagination support.
🧭 Four-Question Framework
1. What problem does this solve?
Multiple UX pain points with cursor-agent Discord slash commands:
- Discord 3-second interaction deadline causes failures on cold-start
- Users hit "start a conversation first" dead-end when no conversation exists
- cursor-agent uses "mode" terminology while kiro-cli uses "agent" — confusing for users
- docs/cursor.md needed updating for
--model autosemantics
2. How does it solve it?
Changes across 3 files (+152/-58):
defer_ephemeral→edit_responsepattern to handle Discord's 3s deadlineget_or_createpre-flight check to auto-create conversations- Agent/mode category alias so both terminologies work
- docs/cursor.md overhaul with known limitations section
3. What was considered?
The PR introduces DISCORD_SELECT_MAX = 25 as a new constant, but main already has SELECT_MENU_PAGE_SIZE = 25 serving the same purpose. The take(25) approach was likely written before main gained full pagination support.
4. Is this the best approach?
The defer_ephemeral, get_or_create, and alias features are solid improvements. However, the select menu handling needs rework to integrate with main's pagination system rather than replacing it with flat truncation.
🚦 Traffic Light
🟢 INFO — defer_ephemeral → edit_response pattern is the correct solution for Discord's 3-second interaction deadline. Well implemented.
🟢 INFO — get_or_create pre-flight eliminates a real UX dead-end. Good improvement.
🟢 INFO — Agent/mode alias is a thoughtful cross-tool consistency fix.
🟢 INFO — docs/cursor.md overhaul with --model auto semantics and known limitations is valuable.
🔴 SUGGESTED CHANGES — PR is in CONFLICTING state. Needs rebase against main.
🔴 SUGGESTED CHANGES — Pagination regression: Main's build_config_select now takes a page parameter with full ◀/▶ pagination. This PR's take(25) approach drops that support entirely. After rebase, please integrate with the existing pagination system instead of truncating.
🟡 NIT — After rebase, use the existing SELECT_MENU_PAGE_SIZE constant instead of introducing DISCORD_SELECT_MAX. Same value, one source of truth.
🟡 NIT — The aliases slice allocation on every call is unnecessary overhead. A simple category == "agent" || category == "mode" condition would be cleaner and zero-alloc.
Review by 超渡法師 🙏 — the net-new features are 🔥, just need to harmonize with main's pagination after rebase!
超渡法師 Review — PR #4921. What problem does it solve?Discord slash commands 2. How does it solve it?
3. What was considered?Three iterations documented in commit history:
4. Is this the best approach?The core changes (defer pattern, alias, take(25), trust-the-protocol) are all correct. However, the PR was branched before Traffic Light🟢 INFO — Well done
🔴 SUGGESTED CHANGES
🟡 NIT
Verdict🔴 Changes requested — The core approach is sound, but the PR needs a rebase onto current |
CHANGES REQUESTED 🔴 — Rebase required (168 commits behind, merge conflicts)The code changes are solid — three meaningful improvements to the Discord slash command flow — but the branch cannot merge in its current state. Please rebase onto 四問框架 Analysis1. What problem does this solve?Discord slash commands (
2. How does it solve them?Three changes in
Additionally:
3. What was considered?The PR description documents three iterations:
Good evolution. Each simplification is well-justified. 4. Is this the best approach?The defer + get_or_create + aliasing approach is correct. Two concerns:
Traffic Light🔴 SUGGESTED CHANGES
🟡 NIT
🟢 INFO
Action needed: Rebase onto |
🔃 Backlog Sweep — 2026-05-03Status: 🔴 Stale — rebase required No new commits since last review (2026-05-03). Branch still has merge conflicts against
The core changes (defer_ephemeral, get_or_create pre-flight, category aliasing) are solid. Waiting on contributor to rebase and address review feedback. Label: |
|
Closing due to staleness — merge conflicts, no contributor activity. The feature can be re-proposed in a fresh PR against current |
Pull request was closed
Closes #489.
Discussion: https://discord.com/channels/1491295327620169908/1491365150664560881/1495774750226645133
Upstream cursor-agent ACP compliance gaps tracked separately in #493.
Summary
/models,/agents,/cancelnow work end-to-end againstcursor-agent acp.defer_ephemeral→pool.get_or_create(thread_key)→ build select menu from the session's cachedconfigOptions→edit_response. No more "start a conversation first" dead-end, and the 3s Discord interaction deadline is honoured even on cold-start sessions.take(25)— no vendor-specific pinning. cursor-agent returns 26 models on a Pro account; the last one is dropped.agent/modecategory treated as aliases so the same/agentshandler works against both kiro-cli (category=agent) and cursor-agent (category=mode). Per ACP spec the reserved value is"mode".set_config_option. If cursor-agent accepts an unroutable model withsetOk:trueand then soft-rejects on the next prompt, that soft-reject is passed through to the channel. Upstream bug tracked in tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493.Changes
src/discord.rs— newhandle_config_command(shared by /models and /agents),handle_config_select,handle_cancel_command;build_config_selectwith ACP-agnostic 25-option cap; category alias table.config.toml.example— documents the--model autostartup gotcha in the cursor-agent section.docs/cursor.md— Model Selection section with startup--model autotable and passthrough semantics; new Known Limitations subsection listing three upstream cursor-agent ACP compliance gaps (all linked to tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493).No changes in
src/acp/connection.rs,src/acp/pool.rs, orsrc/config.rs— ACP layer untouched.Test plan
cargo checkcleancargo test --release— 74/74 passopenab-cursorpod running cursor-agent2026.04.15-dccdccd:/modelslists 25 models (truncated from 26) with the current value highlightedcomposer-2) →✅ Switched to composer-2, next prompt routes there/agentslists Agent / Plan / Ask (regression from v0.7.8 where it returned "No agent options available" against cursor-agent)/cancelagainst an in-flight prompt →session/cancel→stopReason: cancelled(~15ms roundtrip, verified in pod logs)"\n\nCheck your settings to continue"for an unroutable model and OpenAB forwarded it unchanged (new variant beyond theAI Model Not Found/Model name is not validfamily, noted for tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493)Revision history
ping, detected cursor-agent's plain-text soft-reject, and auto-fell-back todefault[]on hit.set_config_optionsemantics intact and surfaces the upstream bug directly in-channel."default[]") also removed — kept as a plain in-ordertake(25)sobuild_config_selecthas no vendor literals.🤖 Generated with Claude Code